#include "Game.h"
#include "DXUtil.h"
#include <d3dx9.h>
#include <d3d9.h>
#include <iostream>
#include <float.h>
#include "ModelPrimitive.h"
#include "TranslateX.h"
#include "Ray.h"
#include "Sphere.h"
#include "Plane.h"
#include "math.h"
#include "HTCalc.h"
#include "NoiseSphere.h"
#include "time.h"
#include <vector>


//#define	NEAR			10.0f
//#define FAR			4000.0f
//#define	FOV			0.8f
#define	WIDTH			640.0f
#define	HEIGHT			480.0f
#define	WIDTH_DIV_2		(WIDTH*0.5f)
#define	HEIGHT_DIV_2		(HEIGHT*0.5f)
#define ASPECT			1.3333f

#define SUPER_SAMPLING_RATE   5
#define SUPER_SAMPLING	false
#define MAX_SAMPLING 10.0f

RAY Game::calcRay( Camera &cam, int x,int y,D3DXVECTOR3 &p1,D3DXVECTOR3 &p2)
{
	//create the ray for pixel x,y
	D3DXVECTOR3 pointInWorldSpace;
	D3DVIEWPORT9 viewport;
	d3ddev->GetViewport(&viewport);
	D3DXMATRIX identityMatrix;
	D3DXMatrixIdentity(&identityMatrix);
	D3DXVec3Unproject(&pointInWorldSpace, &D3DXVECTOR3(x,y,0), &viewport, cam.GetProjectionMatrix(), cam.GetViewMatrix(), &identityMatrix);

	D3DXVECTOR3 returnVector;
	D3DXVec3Normalize(&returnVector,&D3DXVECTOR3(pointInWorldSpace - *cam.GetEyePos()));

	RAY returnRay;
	returnRay.direction = returnVector;
	returnRay.origin = *cam.GetEyePos();

	return returnRay;
}

D3DXCOLOR* Game::getHypertextureColor(Camera &cam, RAY ray)
{
	//set the intial color to black
	D3DXCOLOR* color = &D3DXCOLOR(0.0f,0.0f,0.0f,0.0f);

	Primitive* closestPrimitive;

	//find which spheres the ray intersects
	NoiseSphere* curPrim;
	mIntSize = 0;
	for(int i = 0; i < mListSize; i++)
	{
		curPrim = &mObjectList[i];
		float distance = curPrim->Intersect(ray);
		if( distance != FLT_MAX && distance >0)
		{
			mIntersects[mIntSize] = mObjectList[i];
			mIntSize++;
		}
	}

	//set alpha to zero
	float alphaCur = 0.0f;
	D3DXCOLOR* curColor = &D3DXCOLOR(0,0,0,0);
	int k = 0;

	//reset step and total density
	float step = 0.01f;
	float totalDensity = 0.0f;
	float cont = 0.0f;

	RAY p1;
	p1.origin = ray.origin ;
	::D3DXVec3Normalize(&p1.direction, &ray.direction);

	//continue until you hit 20 units from camera or total density of pixel is 1
	while(cont < 20 && mIntSize >0 && totalDensity <1.0f){

		D3DXVECTOR3 deltaU = (p1.direction * step);
		D3DXVECTOR3 x = p1.origin + (k * deltaU);

		//loop through all spheres and add their density at current sample point
		int i = 0;
		float value = 0.0f;
		while (value < 1.0f && i < mIntSize){	
			//check if point is in sphere to avoid unnecessary denisty calculations
			if(::D3DXVec3Length(&(x-mIntersects[i].center)) <= 2.0f*mIntersects[i].radius){
				value += mIntersects[i].Density(x);;
			}
			i++;
		}
		
		//cap density at 1
		if(value > 1.0f){
			value = 1.0f;
		}

		//calculate alpha
		float alpha = 1.0f - ::pow(1.0f-value, 1.0f * step);

		//keep track of total alpha
		totalDensity += alpha;
		
		//account for extra alpha over 1
		if(totalDensity >1.0f){
			alpha -= totalDensity - 1;
			totalDensity  = 1.0f;
		}

		//calculate color at sample point and add to pixel
		*curColor += alpha*D3DXCOLOR(1.0f,1.0f * value,0.0f,0.0f);

		//check how far we've traveled along ray
		cont = ::D3DXVec3Length(&(x-p1.origin));

		//increment step counter
		k++;
	}
	color = curColor;

	return color;
	
}

void Game::AverageColors(D3DXCOLOR* output, D3DXCOLOR* color[], int n)
{
	//D3DCOLOR* sum;
	float sum[3];
	sum[0] = 0;
	sum[1] = 0;
	sum[2] = 0;

	for(int i = 0; i < n; i++)
	{
		sum[0] += color[i]->r;
		sum[1] += color[i]->g;
		sum[2] += color[i]->b;
	}
	sum[0] /= (float)n;
	sum[1] /= (float)n;
	sum[2] /= (float)n;

	output = &D3DXCOLOR(sum[0],sum[1],sum[2],255);
}

void Game::Initialize()
{
	frameCount = 0;
	D3DXMatrixIdentity(&mIdentity);
	IniScene();
	//LoadContent(d3ddev);
}

void Game::Update( double pElapsedTime) 
{
	ft->Update(pElapsedTime);
	mObjectList = ft->GetPrimitives(&mListSize);
}

D3DVECTOR* Game::DetermineEllipseTangent(D3DVECTOR* circle,D3DVECTOR* point )
{
     // This is just an implementation of the equation in the tutorial. All we need is a vector
     // giving the direction of the tangent, hence we only pay attention to the final terms.
 
     // The variable t is simply a parameter to the equation. We are only paying attention to
     // the final terms, and since they give the slope (i.e., the direction of the tangent), we
     // let t = 1.
 
   //  D3DVECTOR tangent = new D3DVECTOR();
    // tangent.x = point->x - (Ellipse->z* point->y)/Ellipse->z;
     //tangent.y = point->y + (Ellipse->z*point->x)/Ellipse->z;
	return NULL;
   //  return &tangent;     
}

void Game::IniScene(){
	
	
	mCamera = new Camera();

	D3DVIEWPORT9 view;
	d3ddev->GetViewport( &view);
	mCamera->SetAspectRatio(((float)view.Width)/view.Height);
	
	mCamera->GetEyePos()->x = 0.0f;
	mCamera->GetEyePos()->y = 0.0f;
	mCamera->GetEyePos()->z = -12.0f;
	
	mCamera->GetLookAt()->x = 0.0f;
	mCamera->GetLookAt()->y = 0.0f;
	mCamera->GetLookAt()->z = 0.0f;

	mCamera->SetFov(D3DXToRadian(60.0f));

	D3DXVECTOR3	temp1, temp2;

	LIGHT* tempLight = new LIGHT();
	tempLight->position = D3DXVECTOR3(-10,10,0);
	mLights.push_back(tempLight);

	LIGHT* tempLight2= new LIGHT();
	tempLight2->position = D3DXVECTOR3(10,10,0);
	mLights.push_back(tempLight2);

	ambientLight = D3DXCOLOR(255,255,255,255);
	ambientStrength = .1f;

	image = new D3DXCOLOR[(int)(HEIGHT*WIDTH)];

	int index = 0;
	for(int i =0;  i < HEIGHT; i++){
		for(int j =0;  j < WIDTH; j++){
			index = i*WIDTH + j;
			image[index].a = 255.0f;
			image[index].r = 0.0f;
			image[index].g = 0.0f;
			image[index].b = 0.0f;
		}
	}
	index =0;

	ft = new FractalTree();

	HTCalc::Initialize();
}

void Game::LoadContent(){
	for(int i = 0; i < mModels.size(); i++ )
	{
		mModels[i]->LoadContent(d3ddev);
	}
}

//Draws the list of models
void Game::Draw(){
	mIntersects = new NoiseSphere[mListSize];
	D3DXCOLOR* colorArray[SUPER_SAMPLING_RATE];
	D3DXCOLOR* color = &D3DXCOLOR(0,0,0,255);

	int index = 0;
	for(int i = 0; i <WIDTH; i++)
	{
		for(int j = 0; j < HEIGHT; j++)
		{
			index = j*WIDTH + i;
			RAY ray = this->calcRay(*mCamera, i, j, D3DXVECTOR3(), D3DXVECTOR3());
			ray.inside = false;
			color = this->getHypertextureColor(*mCamera, ray);

			image[index].a = color->a;
			image[index].r = color->r;
			image[index].g = color->g;
			image[index].b = color->b;
		}
	}
}

void Game::ScreenShot(){

	string s = "frame";
		
		std::string frames;
		std::stringstream out;
		out << frameCount++;
		frames = out.str();
		
		s.append(frames);
		s.append(".png");

		//LPDIRECT3DSURFACE9		Surface;
		::IDirect3DSurface9* Surface;

		// Grab the current image in our front buffer.
		d3ddev->CreateOffscreenPlainSurface( 640, 480, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &Surface, NULL );

		// Copy everything from the front buffer to our surface.
			d3ddev->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO,&Surface );

		// Write the contents of the surface to the given file.
		D3DXSaveSurfaceToFile( s.c_str(), D3DXIFF_BMP, Surface, NULL, NULL);

		// Finally, free the image surface.
		Surface->Release( );
		Surface = NULL;

}

void Game::Release(){
	for(int i = 0; i < mModels.size(); i++ )
	{
		mModels[i]->Release();
	}
	delete mCamera;
}


// this function initializes and prepares Direct3D for use
void Game::initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = 640;
    d3dpp.BackBufferHeight = 480;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

	d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

	d3ddev->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0); // clear frame
	d3ddev->Present(NULL,NULL,NULL,NULL);
    return;
}


void Game::Present(){
	IDirect3DSurface9 *backBuffer = NULL;
	//d3ddev->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 255, 0), 1.0f, 0); // clear frame

	d3ddev->BeginScene();
	d3ddev->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO, &backBuffer);

	D3DXCOLOR* color = NULL;
	int index = 0;
	for(int i = 0; i <WIDTH; i++)
	{
		for(int j = 0; j < HEIGHT; j++)
		{
			index = j*WIDTH + i;
			RECT fillRect;
			fillRect.bottom = j+1;
			fillRect.top = j;
			fillRect.right = i+1;
			fillRect.left = i;		
			color =  &image[index];
			d3ddev->ColorFill(backBuffer, &fillRect, *color);
		}
	}

	d3ddev->EndScene();
	d3ddev->Present(NULL,NULL,NULL,NULL);
}
